﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using Chat.Server;

namespace Chat.Client
{
    public class ServerCmdEventArgs22<Cmd> : EventArgs where Cmd : ServerCmd
    {
        public Cmd ServerCmd { get; protected set; }

        public ServerCmdEventArgs22(Cmd serverCmd)
        {
            ServerCmd = serverCmd;
        }
    }

    public class LoginFailedException : ApplicationException
    {
        public LoginFailedCmd LoginFailedCmd { get; protected set; }

        public LoginFailedException(LoginFailedCmd loginFailedCmd)
        {
            LoginFailedCmd = loginFailedCmd;
        }

        public override string Message
        {
            get
            {
                return LoginFailedCmd.Reason;
            }
        }
    }

    public class NotLoggedInException : ApplicationException
    {
        public NotLoggedInException() :
            base("Client Not Logged In")
        {
        }
    }

    public class BaseClient
    {
        protected Connection<Server.ServerCmd, ClientCmd> Connection { get; set; }
        protected CmdSerializer<Server.ServerCmd, ClientCmd> Serializer { get; set; }
        protected ServerCmdProcessor CmdProcessor { get; set; }
        protected CmdProcessorThread<ServerCmd> CmdProcessorThread { get; set; }
        public LoginData LoginData { get; protected set; }

        public BaseClient(
            LoginData loginData,
            Connection<Server.ServerCmd, ClientCmd> connection,
            CmdSerializer<Server.ServerCmd, ClientCmd> serializer)
        {
            Connection = connection;
            Serializer = serializer;

            CmdProcessor = new ServerCmdProcessor();
            CmdProcessorThread = new CmdProcessorThread<ServerCmd>(
                CmdProcessor, Connection.GetCommandReceiver(Serializer));

            // send login command to server
            SendCommand(new LoginCmd(loginData));

            // expect to receive LoginResponseCmd in response to the login command
            ServerCmd responseCmd = ReceiveCommand();

            // exception is the only legal way to return error codes from constructors
            if (responseCmd is LoginFailedCmd)
                throw new LoginFailedException(responseCmd as LoginFailedCmd);

            // we are expecting for either LoginFailedCmd or LoginOkCmd only
            if (!(responseCmd is LoginOkCmd))
                throw new UnexpectedCmdException(responseCmd);

            // at this point we know for sure that responseCmd is LoginOkCmd, good!
            LoginData = loginData;
        }

        public event EventHandler<IllegalCmdExEventArgs> IllegalCmdReceived
        {
            add { CmdProcessorThread.IllegalCmdReceived += value; }
            remove { CmdProcessorThread.IllegalCmdReceived -= value; }
        }
        public event EventHandler<DisconnectedExEventArgs> Disconnected
        {
            add { CmdProcessorThread.Disconnected += value; }
            remove { CmdProcessorThread.Disconnected -= value; }
        }
        public event EventHandler<ReceiveMsgEventArgs> MessageReceived
        {
            add { CmdProcessor.MessageReceived += value; }
            remove { CmdProcessor.MessageReceived -= value; }
        }
        public event EventHandler<UsersOnlineEventArgs> UsersUpdate
        {
            add { CmdProcessor.UsersUpdate += value; }
            remove { CmdProcessor.UsersUpdate -= value; }
        }

        public void Start()
        {
            CmdProcessorThread.Start();
        }

        public void SendMessage(NickName to, string message)
        {
            MessageData msgData = new MessageData(to, message, LoginData.User);
            SendMessageCmd cmd = new SendMessageCmd(msgData);

            SendCommand(cmd);
        }

        public void SendMessage(string message)
        {
            SendMessage(NickName.Broadcast, message);
        }

        public virtual void Logout()
        {
            SendCommand(new LogoutCmd(LoginData.User));

            Close();
        }

        public virtual void Close()
        {
            CmdProcessorThread.Stop();
            Connection.Close();
        }

        protected void SendCommand(ClientCmd cmd)
        {
            Connection.SendCmd(cmd, Serializer);
        }

        protected ServerCmd ReceiveCommand()
        {
            return Connection.ReceiveCmd(Serializer);
        }
    }
}
